/* let name = "赵蕊";
let age = 18;
let text = "javascript";
/!*
${}：向模板字符串中嵌入指定的JS表达式「运行有返回结果的语句」
  + 变量
  + 数学运算
  + 三元运算符  ${age>25?'男':'女'}
  + ...
*!/
console.log(`我的名字叫做：${name}，我的年龄是：${age}岁，我正在学习：${text}`); //ES6模板字符串有助于字符串拼接
// console.log("我的名字叫做：" + name + "，我的年龄是：" + age + "岁，我正在学习：" + text); //传统字符串拼接 */

/*
 symbol唯一值
   + 每一次执行 Symbol([descipt]) 创建一个唯一值，例如：Symbol('AA')...
     Symbol('AA')===Symbol('AA') => false
   + 不能 new Symbol() 这样执行
     Uncaught TypeError: Symbol is not a constructor
   + 一个对象的属性名可以是“字符串”类型，也可以是“symbol”类型！！

  let sy = Symbol();
  let obj = {
    name: '珠峰',
    0: 100,
    // 中括号只是为了保证语法正常
    [Symbol()]: 200,
    [sy]: 300
  };
  console.log(obj[0]); //100
  console.log(obj["0"]); //100
  console.log(obj[Symbol()]); //undefined 获取时候和创建时候用的不是想通的唯一值
  console.log(obj[sy]); //300
*/

/*
 浏览器有最大/最小安全数字:超过安全数字进行运算，结果是不准确的
   Number.MAX_SAFE_INTEGER/MIN_SAFE_INTEGER
     + 9007199254740991
     + -9007199254740991
   不能 new BigInt() 执行，会报错！！

 服务器端对于数字有：longInt、shortInt、Int这样的区分，如果服务器端返回longInt长整型的数字，客户端获取到之后，进行运算可能会导致丢失精准度「原因超过最大安全数字了」
   + 先把获取的字符串(服务器会把超长数字变为字符串)变为BigInt类型
   + 再基于BigInt进行运算「需要和另外一个BigInt类型进行运算」
   + 最后把运算的结果变为字符串传递给服务器

// BigInt("90071992547409932324") //90071992547409932324n
let res = BigInt("90071992547409932324") + 1n;
console.log(String(res));
 */


/*
JS中的数据类型
   原始值类型「基本数据类型 & 值类型」
    + number 数字：整数、小数、零、负数、NaN(不是一个有效数字，但是属于数字类型)、Infinity(无穷大的值)...
    + string 字符串：“”、‘’、``(ES6中的模板字符串，更方便的实现字符串拼接)
    + boolean 布尔：true/false
    + null 空
    + undefined 未定义
    + symbol 唯一值(ES6+)
    + bigint 大数(ES6+)
   对象类型「引用数据类型」
    + 标准普通对象  例如：{x:10,y:20}
    + 标准特殊对象
      + new Array 数组
      + new RegExp 正则
      + Math 数学函数对象
      + new Date 日期对象
      + new Error 错误对象
      + Set/Map 「ES6+新增的数据结构 」
      + ...
    + 非标准特殊对象  例如：new Number(1) -> 原始值对应的对象数据类型值
    + 函数对象function


typeof数据类型检测的底层机制
  特点1：返回的结果是字符串，字符串中包含了对应的数据类型
    + typeof typeof typeof [1,2,3]
      typeof [1,2,3] -> "object"
      typeof "object" -> "string"
      ...
      => "string"

  特点2：按照计算机底层存储的二进制进行检测「效率高」
    + 计算机科学：计算机原理、进制转化、计算机网络、数据结构和算法……
    + 000 对象
    + 1 整数
    + 010 浮点数
    + 100 字符串
    + 110 布尔
    + 000000… null
    + -2^30 undefined
    + ……
    ------
    typeof按照二进制进行检测的时候，认为以“000”开始的就是对象类型，因为null存储的是64个零，所以被识别为对象（但是它并不是对象），导致：typeof null -> “object”
    如果检测出来是对象，再去看是否实现了call方法：如果实现了，说明其是一个函数对象，返回“function”；如果没有实现call，都返回“object”；

  特点3：typeof null -> “object”
  特点4：typeof 对象 -> “object” && typeof 函数 -> “function”
    + typeof不能检测null，也无法对“对象”进行细分（除函数对象外）
    + 验证是否是对象的判断
    let n = null;
    if (n !== null && (typeof n === "object" || typeof n === "function")) {
      console.log('n是个对象');
    }

  特点5：typeof 未被声明的变量 -> “undefined”
    + 可以基于typeof判断兼容性
    // console.log(a); //Uncaught ReferenceError: a is not defined  未被声明的变量，我们直接访问会报错
    // console.log(typeof a); //"undefined"  基于typeof检测未被声明的变量，不会报错，结果是“undefined”
    if (typeof Symbol !== "undefined") {
      // 说明当前浏览器是支持Symbol的「非IE浏览器」
      // ...
    }
*/
//===============================================
/*
let arr = [10];
console.log(Number(arr)); //10
// @1 arr[Symbol.toPrimitive] -> undefined 不具备这个属性
// @2 arr.valueOf() -> [10] 不是原始值
// @3 arr.toString() -> '10' 把其变为字符串
// @4 Number('10') -> 10 最后把字符串变为数字
*/

/*
let time = new Date();
console.log(Number(time)); //1652676834978
// @1 time[Symbol.toPrimitive]('number') -> 1652676834978
//   传递的参数：'number'、'string'、'default'
*/

/*
let num = new Number(10); //->typeof num==="object"
console.log(Number(num)); //10
// @1 num[Symbol.toPrimitive] -> undefined
// @2 num.valueOf() -> 10
*/

/* console.log(Number('12px')); //NaN
console.log(parseInt('12px')); //12

console.log(Number('')); //0
console.log(parseInt('')); //NaN

console.log(Number(null)); //0
console.log(parseInt(null)); //parseInt('null') -> NaN

console.log(Number([10, 20])); //Number('10,20') -> NaN
console.log(parseInt([10])); //String([10]) -> parseInt('10,20') -> 10 */


/* parseInt('128px32'); //->parseInt('128px32',10)
// 先找到所有符合10进制的内容 -> “128”
// 然后把“128”看做10进制,转换为10进制 => 128 */
/* parseInt('12684px', 8);
// 先找到符合8进制的内容 -> '126'
// 然后把“126”看做8进制，转换为10进制 「把其它进制的值转换为10进制，规则：按权展开求和（N进制转10进制，把每一位展开相加，每一位乘以 N^权重 ；权重按照个位是零、十位是1...）」
// 1*8^2+2*8^1+6*8^0=64+16+6=86 */


/*
把其他数据类型转换为Number
  Number([val])
    + 一般用于浏览器的隐式转换中
        @1 数学运算
        @2 isNaN检测
        @3 ==比较
        ...
    + 规则：
        @1 字符串转换为数字：空字符串变为0，如果出现任何非有效数字字符，结果都是NaN
        @2 把布尔转换为数字：true->1  false->0
        @3 null->0  undefined->NaN
        @4 Symbol无法转换为数字，会报错：Uncaught TypeError: Cannot convert a Symbol value to a number
        @5 BigInt去除“n”（超过安全数字的，会按照科学计数法处理）
        @6 把对象转换为数字：
          + 先调用对象的 Symbol.toPrimitive 这个方法，如果不存在这个方法
          + 再调用对象的 valueOf 获取原始值，如果获取的值不是原始值
          + 再调用对象的 toString 把其变为字符串
          + 最后再把字符串基于Number方法转换为数字
  parseInt([val],[radix])  parseFloat([val])
    + 一般用于手动转换
    + 规则：[val]值必须是一个字符串，如果不是则先转换为字符串；然后从字符串左侧第一个字符开始找，把找到的有效数字字符最后转换为数字「一个都没找到就是NaN」；遇到一个非有效数字字符，不论后面是否还有有效数字字符，都不再查找了；parseFloat可以多识别一个小数点；
  parseInt([val],[radix])
    从[val]左侧开始进行查找，找到所有符合[radix]进制的内容，然后把其按照[radix]进制转换为10进制！！
    + [radix]是设置的进制，取值有效范围是2~36之间，如果不在有效范围内，结果就是NaN
    + [radix]不写或者设置的为0，默认就是10「特殊情况：如果[val]是以“0x”开始的字符串，则默认值是16」


把其他数据类型转换为String
  转化规则：
    @1 拿字符串包起来
    @2 对象转字符串
      + String(对象)：按照 先找Symbol.toPrimitive -> 再看valueOf -> 最后toString 来处理
      + 对象.toString()：直接转换为字符串
      + 特殊：Object.prototype.toString，是用来检测数据类型的
  出现情况：
    @1 String([val]) 或者 [val].toString()
    @2 “+”除数学运算，还可能代表的字符串拼接
        + 有两边，一边是字符串，肯定是字符串拼接
        + 有两边，一边是对象，则可能是字符串拼接，还有可能是数学运算
        + 只出现在左边
        + ...
    ...


把其他数据类型转换为Boolean
  转换规则：除了“0/NaN/空字符串/null/undefined”五个值是false，其余都是true
  出现情况：
    @1 Boolean([val]) 或者 !/!!
    @2 条件判断
    ...


“==”相等，两边数据类型不同，需要先转为相同类型，然后再进行比较
    @1 对象==字符串  对象转字符串「Symbol.toPrimitive -> valueOf -> toString」
    @2 null==undefined  -> true   null/undefined和其他任何值都不相等
       null===undefined -> false
    @3 对象==对象  比较的是堆内存地址，地址相同则相等
    @4 NaN!==NaN  NaN和任何值(包含本身)都不相等 
    @5 除了以上情况，只要两边类型不一致，剩下的都是转换为数字，然后再进行比较的
“===”绝对相等，如果两边类型不同，则直接是false，不会转换数据类型「推荐」

Object.is([val1],[val2]) 检测两个值是否相等「ES6新增的」
  + 核心用的是“===”
  + 特殊：Object.is(NaN,NaN) => true
*/

/*
let num = parseFloat('width:100px'); //NaN
if (num == 100) {
  alert(1);
} else if (num == NaN) { //NaN!==NaN
  alert(2);
} else if (typeof num == 'number') {
  alert(3); //弹出“3”  alert会把所有内容以字符串格式输出：alert([val]) -> String([val])
} else {
  alert(4);
}
*/

/* // map:依次循环数据中的每一项，每一次循环，把传递进来的回调函数执行
let arr = [10, 20, 30];
let arr2 = arr.map(function (item, index) {
  // 回调函数触发执行三次
  // item:当前循环这一项的值  index当前循环这一项的索引
  //   第一次：item=10 index=0  返回0
  //   第二次：item=20 index=1  返回20
  //   第三次：item=30 index=2  返回60
  // 回调函数执行的返回结果，会替换数组中这一项，原始数组不变，以新数组形式返回「forEach不支持这个返回值」
  return item * index;
});
console.log(arr); //[10, 20, 30]
console.log(arr2); //[0, 20, 60] */

/* let arr = [27.2, 0, '0013', '14px', 123];
arr = arr.map(parseInt);
console.log(arr); //[27,NaN,1,1,27] */
/*
循环(迭代/遍历)数组每一项(5次)，每一次循环执行parseInt，并且把当前项和当前项的索引传递给它，方法执行的返回结果会替换数组中的这一项，以新数组形式返回！！
  parseInt(27.2,0) -> parseInt('27.2',10) => 27
  parseInt(0,1) => NaN  因为进制不在有效范围内
  parseInt('0013',2)
    '001' 把其当做2进制转换为10进制
    0*2^2+0*2^1+1*2^0 => 1
  parseInt('14px',3)
    '1' 把其当做3进制转换为10进制
    1*3^0 => 1
  parseInt(123,4) -> parseInt('123',4)
    '123' 把其当做4进制转换为10进制
    1*4^2+2*4^1+3*4^0 => 16+8+3 => 27
*/

//------------------
// let result = 100 + true + 21.2 + null + undefined + "Tencent" + [] + null + 9 + false;
/*
 100 + true -> 101
 101 + 21.2 -> 122.2
 122.2 + null -> 122.2
 122.2 + undefined -> NaN
 NaN + "Tencent" -> "NaNTencent"  接下来向后加的任何东西都是字符串拼接
 .....
 "NaNTencentnull9false"
 */

/* console.log(1 + [10]);
// 尝试把[10]转换为数字
//   [10][Symbol.toPrimitive] -> undefined
//   [10].valueOf() -> [10]
//   [10].toString() -> "10"
// 在相加处理的过程中，遇到了字符串，则直接变为字符串拼接 “110” */

/* console.log(1 + new Number(10));
// 尝试把new Number(10)转换为数字
//   new Number(10)[Symbol.toPrimitive] -> undefined
//   new Number(10).valueOf() -> 10
// 此时运算变为1+10 => 11 */

/*
i++和++i
  相同：都是自身累加1
  区别：i++是先运算再累加、++i先累加再运算

i++和i+=1（i=i+1）是否一样?
  @1 i++ 不论i的值是啥类型，最后都是数学运算
  @2 i+=1（i=i+1）可能会出现字符串拼接的情况

  let i = '10';
  i++;
  console.log(i); //11

  i = '10';
  i += 1;
  console.log(i); //'101'

  i = '10';
  i = i + 1;
  console.log(i); //'101'
*/
// let num = "10";
// num = +num; //把num转换为数字，等同于Number(num)



//------------------
/* console.log([] == false); //true
// 两边都转换为数字
//  [] -> Number([]) -> 0
//  false -> Number(false) -> 0

console.log(![] == false); //true
// 先算 ![]：把[]转换为布尔类型，然后再取反 -> false
// false == false */

// a等于啥值，可以让条件成立？
// 思路一：“==”比较，两边类型不一致，会默认进行数据类型转换；对象转数字，会经历 Symbol.toPrimitive -> valueOf -> toString；这样：只要让a是一个对象，我们去重写Symbol.toPrimitive/valueOf/toString三个方法中的任何一个方法，让方法每一次执行，分别返回1/2/3即可！
/* var a = {
  i: 0,
  [Symbol.toPrimitive]: function () {
    return ++this.i;
  }
};
// a转为数字 -> a[Symbol.toPrimitive]()
if (a == 1 && a == 2 && a == 3) {
  console.log('OK');
} */

/* 
// shift方法：删除数组第一项，返回删除的这一项内容，原始数组改变
var a = [1, 2, 3];
a.toString = a.shift;
// 每一次比较都会调用 a.toString() -> a.shift()
if (a == 1 && a == 2 && a == 3) {
  console.log('OK');
} 
*/

/* // 思路二：当前代码执行处于全局上下文「EC(G)」，a变量是基于var声明的，所以这个a存储到了GO中「也就是window对象中设置了一个叫做a的属性，window.a」；我们每一次比较的时候，都是在获取window.a属性的值，这样我们只需要对这个属性做“数据劫持”即可！！
var i = 0;
Object.defineProperty(window, 'a', {
  get() {
    // 对window中的a属性做了GET劫持，以后访问window.a，都会触发这个GET函数执行
    return ++i;
  }
});
if (a == 1 && a == 2 && a == 3) {
  console.log('OK');
} */